/*
 *
 *  Copyright (C) 2010 Tal Eisenberg
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package tal.drivechain.gui;

import java.awt.AWTException;
import java.awt.Cursor;
import java.awt.Image;
import java.awt.Point;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.MemoryImageSource;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;

/**
 *
 * @author eisental
 */
public class DragSlider extends JComponent implements MouseListener, MouseMotionListener {
    private double value;
    protected double max, min, scale;
    private Point clickPoint = null;
    private double baseValue;
    private Robot robot;

    protected enum State { HOVER, DRAG, RELEASED };
    protected State curState = State.RELEASED;
    protected List<ActionListener> actionListeners = new ArrayList<ActionListener>();
    private ActionEvent event = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "change");
    private Cursor noCursor;

    public DragSlider(double value) {
        this(value, 100, 0, 1);
    }

    public DragSlider(double value, double maxValue, double minValue, double scale) {
        super();
        this.value = value;
        this.max = maxValue;
        this.min = minValue;
        this.scale = scale;
        addMouseMotionListener(this);
        addMouseListener(this);
        try {
            robot = new Robot();
            robot.setAutoWaitForIdle(true);
        } catch (AWTException ex) {}
    }

    public void addActionListener(ActionListener listener) {
        actionListeners.add(listener);
    }

    protected void fireAction() {
        for (ActionListener l : actionListeners)
            l.actionPerformed(event);
    }

    public void setValue(double value) {
        this.value = value;
        enforceValueConstraints();
        repaint();
    }

    public double getValue() {
        return value;
    }

    public double getScale() {
        return scale;
    }

    public void setScale(double scale) {
        this.scale = scale;
    }

    public double getMax() {
        return max;
    }

    public double getMin() {
        return min;
    }

    public void setMin(double min) {
        double oldMin = this.min;
        this.min = min;
        enforceValueConstraints();
        if (this.min!=oldMin) {
            fireAction();
            repaint();
        }
    }

    public void setMax(double max) {
        double oldMax = this.max;
        this.max = max;
        enforceValueConstraints();
        if (this.max!=oldMax) {
            fireAction();
            repaint();
        }
    }

    public void mouseClicked(MouseEvent e) {
    }

    public void mousePressed(MouseEvent e) {
        if (e.getButton()!=MouseEvent.BUTTON1) return;
        if (!this.isEnabled()) return;
        clickPoint = e.getPoint();
        baseValue = getValue();
        curState = State.DRAG;
        setEmptyCursor(true);
        repaint();
    }

    public void mouseReleased(MouseEvent e) {
        if (e.getButton()!=MouseEvent.BUTTON1) return;
        if (!this.isEnabled()) return;
        curState = State.RELEASED;
        setEmptyCursor(false);
        resetToClickPoint();
        repaint();
    }

    public void mouseEntered(MouseEvent e) {
        if (!this.isEnabled()) return;
        curState = State.HOVER;
        repaint();
    }

    public void mouseExited(MouseEvent e) {
        if (!this.isEnabled()) return;
        if (curState == State.HOVER) {
            curState = State.RELEASED;
            repaint();
        }
    }

    public void mouseDragged(MouseEvent e) {
        if (!this.isEnabled()) return;
        curState = State.DRAG;
        double delta = getDelta(e.getPoint(), clickPoint, e.isShiftDown());
        double v = delta + baseValue;

        setValue(v);

        clickPoint = e.getPoint();
        baseValue = getValue();

        fireAction();
        repaint();
    }

    public void mouseMoved(MouseEvent e) {
    }

    private void enforceValueConstraints() {
        if (value>max) value = max;
        else if (value<min) value = min;
    }

    private double getDelta(Point dragPoint, Point clickPoint, boolean slowDrag) {
        return (clickPoint.y - dragPoint.y)*(slowDrag?scale/4:scale);
    }

    private void setEmptyCursor(boolean empty) {
        if (empty) {
            if (this.noCursor==null) {
                int[] pixels = new int[16 * 16];
                Image image = Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(16, 16, pixels, 0, 16));
                noCursor = Toolkit.getDefaultToolkit().createCustomCursor(image, new Point(0, 0), "Transparent");
            } this.setCursor(noCursor);
        }  else {
            this.setCursor(Cursor.getDefaultCursor());
        }
    }

    private void resetToClickPoint() {
        if (clickPoint == null) return;

        if (robot!=null) {
            SwingUtilities.convertPointToScreen(clickPoint, this);
            try {
                robot.mouseMove(clickPoint.x, clickPoint.y);
            } catch (Exception e) {}
        }
        clickPoint = null;
    }

}
